﻿/*======================================================================
== Copyright : BlueCurve (c)
== Licence   : Gnu/GPL v2.x
== Author    : Teddy Albina
== Email     : bluecurveteam@gmail.com
== Web site  : http://www.codeplex.com/BlueCurve
========================================================================*/
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using BlueCurve.Common;
using Lucene.Net.Analysis.Standard;
using Lucene.Net.Documents;
using Lucene.Net.Highlight;
using Lucene.Net.Index;
using Lucene.Net.QueryParsers;
using Lucene.Net.Search;


namespace QueryServer
{
    /// <summary>
    /// Class contenant les méthodes de recherche dans la base de données
    /// </summary>
    public class LuceneIndexReader
    {
        #region 'Configuration'

        /// <summary>
        /// Nombre de fields par défaut
        /// </summary>
        private int _nfields = 0;
        /// <summary>
        /// Compteur du nombre de fields
        /// </summary>
        private event Incrementor Increment;
        private delegate void Incrementor(object sender, FieldFind e);
        /// <summary>
        /// Liste des fields contenu dans la requete
        /// </summary>
        private List<string> FieldsList = new List<string>();
        /// <summary>
        /// Objet lecture de l'index
        /// </summary>
        private IndexReader Indexreader { get; set; }
        /// <summary>
        /// Objet recherche dans l'index
        /// </summary>
        private IndexSearcher Indexsearcher { get; set; }
        /// <summary>
        /// Bibliothèque de fonctions communes
        /// </summary>
        private CommonFunctions Common = new CommonFunctions();
        /// <summary>
        /// Parseur
        /// </summary>
        private QueryParser Indexquery { get; set; }
        /// <summary>
        /// Regexp pour la normalisation des fields
        /// </summary>
        private Regex FieldsRegex = new Regex(@"(Site|FileType|Year|Month|Url|Title|Description|Language|IsDesktop|KeyWords|Domain|Type|Date|DC.Creator|DC.Subject|DC.Publisher|DC.Source|DC.Language|DC.Relation|DC.Coverage|DC.Rights|DC.Created|DC.Issued|DC.Modified|DC.Available|DC.Datesubmitted|DC.Contributor):",
                                              RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.Multiline);
        
        
        /// <summary>
        /// Constructeur
        /// </summary>
        public LuceneIndexReader()
        {
        }


        /// <summary>
        /// Constructeur
        /// </summary>
        /// <param name="Indexlocation">Chemin d'accès de l'index</param>
        public LuceneIndexReader(string Indexlocation)
        {
            Increment += new Incrementor(LuceneIndexReader_Increment);
            string[] temp = System.IO.Directory.GetFileSystemEntries(Indexlocation);

            if (temp.Length < 1)
                throw new Exception("No index to search");

            Indexreader = IndexReader.Open(Indexlocation);
            Indexsearcher = new IndexSearcher(Indexreader);
            Indexquery = new QueryParser("Content", new StandardAnalyzer());
        }
 
        #endregion





        #region 'Functions'

         /// <summary>
        /// Permet d'effectuer une recherche dans l'index
        /// </summary>
        /// <param name="querystring">Requete</param>
        /// <returns><![CDATA[List<SearchContract.SearchData>]]></returns>
        public List<BlueCurve.SearchContract.SearchData> Search(BlueCurve.SearchContract.SearchQuery Querydata)
        {
            try
            {
                if (string.IsNullOrEmpty(Querydata.Query))
                    throw new Exception();
                
                // normalisation des fields
                Querydata.Query = FieldsRegex.Replace(Common.Stripspaces(Querydata.Query), new MatchEvaluator(FieldsNorms));

                // Il y a des données dans le cache ????
                if (Querydata.TypeOfQuery == BlueCurve.SearchContract.TypeOfRequest.Simple && Cache<string, string, List<BlueCurve.SearchContract.SearchData>>.GetInstance.Exist(Querydata.Query))
                {
                    List<BlueCurve.SearchContract.SearchData> temp = Cache<string, string, List<BlueCurve.SearchContract.SearchData>>.GetInstance.Get(Querydata.Query);
                    return this.StipQueryResponses(ref temp, Querydata);
                }

                // Création de la requete
                Query query = null;
                if (_nfields == 0)
                {
                    QueryParser parser = new QueryParser("Content", new StandardAnalyzer());
                    query = parser.Parse(Querydata.Query);
                }
                else
                {
                    MultiFieldQueryParser parser = new MultiFieldQueryParser(FieldsList.ToArray(), new StandardAnalyzer());
                    query = parser.Parse(Querydata.Query);
                }    

                Hits hit = null;
                if (Querydata.QueryFilter.Count > 0)
                    hit = Indexsearcher.Search(query, BuildQueryFilterFromKeywordFilter(Querydata.QueryFilter));
                else
                    hit = Indexsearcher.Search(query);
           
                Indexsearcher.Close();
                
                if (hit.Length() < 1)
                    throw new Exception();

                Highlighter highlighter = new Highlighter(new Lucene.Net.Highlight.QueryScorer(query, "Content"));
                highlighter.SetTextFragmenter(new SimpleFragmenter(30));
                StandardAnalyzer Analyzer = new StandardAnalyzer();

                List<BlueCurve.SearchContract.SearchData> _responses = new List<BlueCurve.SearchContract.SearchData>();

                for (int i = 0; i < hit.Length(); i++)
                {
                    Document document = hit.Doc(i);
                    string content = document.Get("Content");
                    Lucene.Net.Analysis.TokenStream tokenStream = Analyzer.TokenStream("Content", new StringReader(content));

                    BlueCurve.SearchContract.SearchData _searchdata = new BlueCurve.SearchContract.SearchData()
                    {
                        Title = document.Get("Title"),
                        Description = highlighter.GetBestFragments(tokenStream, content, 15, " "),
                        Url = document.Get("Url"),
                        Size = document.Get("Size"),
                        Type = document.Get("ContentType"),
                        Language = document.Get("Language"),
                        Domain = document.Get("Domain"),
                        IsDeskTop = bool.Parse(document.Get("IsDesktop")),
                        Date = DateTime.Parse(document.Get("Date")),
                        IndexValue = hit.Score(i),
                        Indexid = hit.Id(i)
                    };

                    if (_searchdata.IsDeskTop)
                        _searchdata.CreationDate = DateTime.Parse(document.Get("CreationDate"));

                    Field[] fields = document.GetFields("Rss");
                    if (fields != null && fields.Length > 0)
                    {
                        for (int a = 0; a < fields.Length; a++)
                        {
                            if (!_searchdata.Rss.Contains(fields[a].StringValue()))
                                _searchdata.Rss.Add(fields[a].StringValue());
                        }
                    }

                    _responses.Add(_searchdata);
                }

                if (Querydata.TypeOfQuery == BlueCurve.SearchContract.TypeOfRequest.Simple && Cache<string, string, List<BlueCurve.SearchContract.SearchData>>.GetInstance.Exist(Querydata.Query))
                    Cache<string, string, List<BlueCurve.SearchContract.SearchData>>.GetInstance.Add(Querydata.Query, Guid.NewGuid().ToString(), _responses);

                return StipQueryResponses(ref _responses, Querydata);
            }
            catch
            {
                Indexsearcher.Close();
                return new List<BlueCurve.SearchContract.SearchData>();
            }
        }



        /// <summary>
        /// Permet d'effectuer une recherche dans l'index
        /// </summary>
        /// <param name="querystring">Requete</param>
        /// <param name="Type">Type de requete</param>
        /// <returns>string</returns>
        public virtual string Search(BlueCurve.SearchContract.SearchQuery Querydata, bool e)
        {
            StringWriter sw = new StringWriter();
            XElement oXElement = null;
            try
            {
                if (string.IsNullOrEmpty(Querydata.Query))
                    throw new Exception();

                // normalisation des fields
                Querydata.Query = FieldsRegex.Replace(Common.Stripspaces(Querydata.Query), new MatchEvaluator(FieldsNorms));

                // Création de la requete
                Query query = null;
                if (_nfields == 0)
                {
                    QueryParser parser = new QueryParser("Content", new StandardAnalyzer());
                    query = parser.Parse(Querydata.Query);
                }
                else
                {
                    MultiFieldQueryParser parser = new MultiFieldQueryParser(FieldsList.ToArray(), new StandardAnalyzer());
                    query = parser.Parse(Querydata.Query);
                }

                Hits hit = null;
                if (Querydata.QueryFilter.Count > 0)
                    hit = Indexsearcher.Search(query, BuildQueryFilterFromKeywordFilter(Querydata.QueryFilter));
                else
                    hit = Indexsearcher.Search(query);

                Indexsearcher.Close();

                if (hit.Length() < 1)
                    throw new Exception();

                Highlighter highlighter = new Highlighter(new Lucene.Net.Highlight.QueryScorer(query, "Content"));
                highlighter.SetTextFragmenter(new SimpleFragmenter(30));
                StandardAnalyzer Analyzer = new StandardAnalyzer();

                List<BlueCurve.SearchContract.SearchData> _responses = new List<BlueCurve.SearchContract.SearchData>();
                List<object> _xmlnodes = new List<object>();

                for (int i = 0; i < hit.Length(); i++)
                {
                    Document document = hit.Doc(i);
                    string content = document.Get("Content");
                    Lucene.Net.Analysis.TokenStream tokenStream = Analyzer.TokenStream("Content", new StringReader(content));
                    
                    XElement elem = new System.Xml.Linq.XElement("document",
                                new System.Xml.Linq.XElement("Title",       new XCData(document.Get("Title"))),
                                new System.Xml.Linq.XElement("Description", new XCData(highlighter.GetBestFragments(tokenStream, content, 15, " "))),
                                new System.Xml.Linq.XElement("Url",         new XCData(document.Get("Url"))),
                                new System.Xml.Linq.XElement("Size",        new XCData(document.Get("Size"))),
                                new System.Xml.Linq.XElement("Type",        new XCData(document.Get("ContentType"))),
                                new System.Xml.Linq.XElement("Language",    new XCData(document.Get("Language"))),
                                new System.Xml.Linq.XElement("Domain",      new XCData(document.Get("Domain"))),
                                new System.Xml.Linq.XElement("IsDeskTop",   new XCData(document.Get("IsDesktop"))),
                                new System.Xml.Linq.XElement("Date",        new XCData(document.Get("Date"))),
                                new System.Xml.Linq.XElement("IndexValue",  new XCData(Convert.ToString(hit.Score(i)))),
                                new System.Xml.Linq.XElement("Indexid",     new XCData(Convert.ToString(hit.Id(i)))));
                                    if(bool.Parse(document.Get("IsDesktop")))
                                    {
                                       elem.Add(new System.Xml.Linq.XElement("CreationDate", new XCData(document.Get("CreationDate"))));
                                    }

                                    Field[] fields = document.GetFields("Rss");
                                    if (fields != null && fields.Length > 0)
                                    {
                                        object[] temp = new object[fields.Length];
                                        for (int a = 0; a < fields.Length; a++)
                                        {
                                            temp[a] = new System.Xml.Linq.XElement("Url", new XCData(fields[a].StringValue()));
                                        }
                                        elem.Add(new System.Xml.Linq.XElement("Rss", temp));
                                    }

                    _xmlnodes.Add(elem);
                }

                oXElement = new XElement("node", _xmlnodes.ToArray());
                oXElement.Save(sw);
                return sw.ToString();
            }
            catch
            {
                Indexsearcher.Close();
                return null;
            }
        }


        /// <summary>
        /// Recherche les meta-données néçessaires
        /// à la mise à jour eventuelle du document
        /// </summary>
        /// <param name="path">Chemin de fichier du document</param>
        /// <returns>BlueCurve.SearchContract.UpdateMetaData</returns>
        public BlueCurve.SearchContract.UpdateMetaData GetMetaDataForUpdate(string path)
        {
            try
            {
                if (string.IsNullOrEmpty(path))
                    return null;

                BlueCurve.SearchContract.UpdateMetaData datas = new BlueCurve.SearchContract.UpdateMetaData();

                QueryParser parser = new QueryParser("Url", new StandardAnalyzer());
                Query query = parser.Parse(path);
                Hits hit = Indexsearcher.Search(query);
                Indexsearcher.Close();

                for (int i = 0; i < hit.Length(); i++)
                {
                    Document document = hit.Doc(i);
                    datas.LastModified = DateTime.Parse(document.Get("LastModified"));
                    datas.IsDesktop = bool.Parse(document.Get("IsDesktop"));
                    if (datas.IsDesktop)
                    {
                        datas.CreationDate = DateTime.Parse(document.Get("CreationDate"));
                    }
                }

                return datas;
            }
            catch
            {
                return null;
            }
        }

        

        /// <summary>
        /// Retourne tout ou partie du tableau de données
        /// </summary>
        /// <param name="_responses">Tableaux de données</param>
        /// <param name="Querydata">Requête</param>
        /// <returns><![CDATA[List<SearchContract.SearchData>]]></returns>
        private List<BlueCurve.SearchContract.SearchData> StipQueryResponses(ref List<BlueCurve.SearchContract.SearchData> _responses, BlueCurve.SearchContract.SearchQuery Querydata)
        {
            if (Querydata.NeedPageIndex > 1)
                return _responses.GetRange(Querydata.NeedPageIndex * Querydata.MaxResponsePerPage, (Querydata.NeedPageIndex * Querydata.MaxResponsePerPage) + Querydata.MaxResponsePerPage);
            else if (_responses.Count > Querydata.MaxResponsePerPage)
                return _responses.GetRange(0, Querydata.MaxResponsePerPage);
            else
                return _responses;
        }



        /// <summary>
        /// Construit un filtre pour la recherche s'il le faut
        /// </summary>
        /// <param name="QueryFilter">Filtre de recherche</param>
        /// <returns>Filter</returns>
        private Filter BuildQueryFilterFromKeywordFilter(Dictionary<string, KeyValuePair<string, BlueCurve.SearchContract.BooleanClause>> QueryFilter)
        {
            BooleanQuery bQuery = new BooleanQuery();
            foreach (KeyValuePair<string, KeyValuePair<string, BlueCurve.SearchContract.BooleanClause>> pair in QueryFilter)
            {
                switch (pair.Value.Value)
                {
                    case BlueCurve.SearchContract.BooleanClause.Must:
                        bQuery.Add(new TermQuery(new Term(FieldsRegex.Replace(pair.Value.Key, new MatchEvaluator(FieldsNorms2)), pair.Key)), BooleanClause.Occur.MUST);
                        break;
                    case BlueCurve.SearchContract.BooleanClause.Must_Not:
                        bQuery.Add(new TermQuery(new Term(FieldsRegex.Replace(pair.Value.Key, new MatchEvaluator(FieldsNorms2)), pair.Key)), BooleanClause.Occur.MUST_NOT);
                        break;
                    case BlueCurve.SearchContract.BooleanClause.Should:
                        bQuery.Add(new TermQuery(new Term(FieldsRegex.Replace(pair.Value.Key, new MatchEvaluator(FieldsNorms2)), pair.Key)), BooleanClause.Occur.SHOULD);
                        break;
                    default:
                        break;
                }
            }

            return new QueryFilter(bQuery);
        }


        /// <summary>
        /// Normalisation des fields
        /// </summary>
        /// <returns>Field normalisé</returns>
        private string FieldsNorms(Match m)
        {
            switch (m.Value.ToLower(System.Globalization.CultureInfo.InvariantCulture))
            {
                case "url:":              Increment("", new FieldFind() { field = "Url" });               return "Url:";
                case "title:":            Increment("", new FieldFind() { field = "Title" });             return "Title:";
                case "description:":      Increment("", new FieldFind() { field = "Description" });       return "Description:";
                case "language:":         Increment("", new FieldFind() { field = "Language" });          return "Language:";
                case "isdesktop:":        Increment("", new FieldFind() { field = "IsDesktop" });         return "IsDesktop:";
                case "keywords:":         Increment("", new FieldFind() { field = "KeyWords" });          return "KeyWords:";
                case "site:":
                case "domain:":           Increment("", new FieldFind() { field = "Domain" });            return "Domain:";
                case "filetype:":
                case "type:":             Increment("", new FieldFind() { field = "Type" });              return "Type:";
                case "date:":             Increment("", new FieldFind() { field = "Date" });              return "Date:";
                case "year:":             Increment("", new FieldFind() { field = "Year" });              return "Year:";
                case "month:":            Increment("", new FieldFind() { field = "Month" });             return "Month";
                case "dc.creator:":       Increment("", new FieldFind() { field = "DC.Creator" });        return "DC.Creator:";
                case "dc.subject:":       Increment("", new FieldFind() { field = "DC.Subject" });        return "DC.Subject:";
                case "dc.publisher:":     Increment("", new FieldFind() { field = "DC.Publisher" });      return "DC.Publisher:";
                case "dc.source:":        Increment("", new FieldFind() { field = "DC.Source" });         return "DC.Source:";
                case "dc.language:":      Increment("", new FieldFind() { field = "DC.language" });       return "DC.language:";
                case "dc.relation:":      Increment("", new FieldFind() { field = "DC.Relation" });       return "DC.Relation:";
                case "dc.coverage:":      Increment("", new FieldFind() { field = "DC.Coverage" });       return "DC.Coverage:";
                case "dc.rights:":        Increment("", new FieldFind() { field = "DC.Rights" });         return "DC.Rights:";
                case "dc.created:":       Increment("", new FieldFind() { field = "DC.Created" });        return "DC.Created:";
                case "dc.issued:":        Increment("", new FieldFind() { field = "DC.Issued" });         return "DC.Issued:";
                case "dc.modified:":      Increment("", new FieldFind() { field = "DC.Modified" });       return "DC.Modified:";
                case "dc.available:":     Increment("", new FieldFind() { field = "DC.Available" });      return "DC.Available:";
                case "dc.datesubmitted:": Increment("", new FieldFind() { field = "DC.Datesubmitted" });  return "DC.Datesubmitted:";
                case "dc.contributor:":   Increment("", new FieldFind() { field = "DC.Contributor" });    return "DC.Contributor:";
                default: return m.Value;
            }
        }


        private string FieldsNorms2(Match m)
        {
            switch (m.Value.ToLower(System.Globalization.CultureInfo.InvariantCulture))
            {
                case "url":               return "Url";
                case "title":             return "Title";
                case "description":       return "Description";
                case "language":          return "Language";
                case "isdesktop":         return "IsDesktop";
                case "keywords":          return "KeyWords";
                case "site":
                case "domain":            return "Domain";
                case "filetype":
                case "type":              return "Type";
                case "date":              return "Date";
                case "year":              return "Year";
                case "month":             return "Month";
                case "dc.creator":        return "DC.Creator";
                case "dc.subject":        return "DC.Subject";
                case "dc.publisher":      return "DC.Publisher";
                case "dc.source":         return "DC.Source";
                case "dc.language":       return "DC.language";
                case "dc.relation":       return "DC.Relation";
                case "dc.coverage":       return "DC.Coverage";
                case "dc.rights":         return "DC.Rights";
                case "dc.created":        return "DC.Created";
                case "dc.issued":         return "DC.Issued";
                case "dc.modified":       return "DC.Modified";
                case "dc.available":      return "DC.Available";
                case "dc.datesubmitted":  return "DC.Datesubmitted";
                case "dc.contributor":    return "DC.Contributor";
                default: return m.Value;
            }
        }

        #endregion





        #region 'Event'

        /// <summary>
        /// Incrémente le compteur de fields
        /// et ajoute les fields trouver dans le tableau de fields
        /// </summary>
        private void LuceneIndexReader_Increment(object sender, LuceneIndexReader.FieldFind e)
        {
            System.Threading.Interlocked.Increment(ref _nfields);
            if (!FieldsList.Contains(e.field) && !string.IsNullOrEmpty(e.field))
                FieldsList.Add(e.field);
        }

        #endregion






        /// <summary>
        /// Données passé à l'évenement
        /// </summary>
        private class FieldFind : EventArgs
        {
            /// <summary>
            /// Nom de field trouvé
            /// </summary>
            public string field { get; set; }
        }
    }
}